Skip to content

Conversation

kmc6123
Copy link

@kmc6123 kmc6123 commented Jan 21, 2024

This is similar to an outstanding PR for two reasons:

  • this PR contains less code and is more explicit imo
  • the outstanding PR is about 2 years old, hoping to revive it as this feature would be helpful for me, my team, and others

Comment on lines +192 to +214
args = {
"match_args": {
"error_message": "'match_args' argument is only available for python >= 3.10",
"default_value": True,
},
"kw_only": {
"error_message": "'kw_only' argument is only available for python >= 3.10",
"default_value": False,
},
"slots": {
"error_message": "'slots' argument is only available for python >= 3.10",
"default_value": False,
},
}

for arg, arg_info in args.items():
if locals()[arg] is not arg_info["default_value"]:
raise ValueError(arg_info["error_message"])

# dataclass's typing doesn't expect it to be called as a function, so ignore type check
dc = dataclasses.dataclass( # type: ignore
_cls, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen
)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this ? Can't we just forward the arguments we got to the base dataclass implementation, and let it error if it wants to ?

Copy link
Collaborator

@dairiki dairiki Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. For the implementation can we not just do

def dataclass(
    __cls: Optional[Type[_U]] = None,
   *,
   base_schema: Optional[Type[marshmallow.Schema]] = None,
   cls_frame: Optional[types.FrameType] = None,
   **kwargs: bool,
 ) -> Union[Type[_U], Callable[[Type[_U]], Type[_U]]]:
    dc = dataclasses.dataclass(**kwargs)

    if not cls_frame:
        current_frame = inspect.currentframe()
        if current_frame:
            cls_frame = current_frame.f_back
        # Per https://docs.python.org/3/library/inspect.html#the-interpreter-stack
        del current_frame

    def decorate(cls: Type[_U]) -> Type[_U]:
        return add_schema(dc(cls), base_schema, cls_frame=cls_frame)

    if _cls is None:
        return decorate
    return decorate(cls)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks the type signature.

Copy link
Collaborator

@dairiki dairiki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left a couple of comments here.

Also, see my review of #205 for more (albeit similar) comments.
https://github.com/lovasoa/marshmallow_dataclass/pull/205/files

slots: bool = False,
base_schema: Optional[Type[marshmallow.Schema]] = None,
cls_frame: Optional[types.FrameType] = None,
) -> Callable[[Type[_U]], Type[_U]]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel these overloads should be conditional on python version. I.e. The prototypes should not declare unsupported options.

Since (I think) we want marshmallow_dataclass.dataclass to have the same signature as dataclasses.dataclass, we should probably just copy typeshed's interface definition for dataclass.

https://github.com/python/typeshed/blob/8b9b0f4d031fe614a0211e1faadf57b3340e643e/stdlib/dataclasses.pyi#L57C1-L104C1.

Comment on lines +192 to +214
args = {
"match_args": {
"error_message": "'match_args' argument is only available for python >= 3.10",
"default_value": True,
},
"kw_only": {
"error_message": "'kw_only' argument is only available for python >= 3.10",
"default_value": False,
},
"slots": {
"error_message": "'slots' argument is only available for python >= 3.10",
"default_value": False,
},
}

for arg, arg_info in args.items():
if locals()[arg] is not arg_info["default_value"]:
raise ValueError(arg_info["error_message"])

# dataclass's typing doesn't expect it to be called as a function, so ignore type check
dc = dataclasses.dataclass( # type: ignore
_cls, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen
)
Copy link
Collaborator

@dairiki dairiki Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. For the implementation can we not just do

def dataclass(
    __cls: Optional[Type[_U]] = None,
   *,
   base_schema: Optional[Type[marshmallow.Schema]] = None,
   cls_frame: Optional[types.FrameType] = None,
   **kwargs: bool,
 ) -> Union[Type[_U], Callable[[Type[_U]], Type[_U]]]:
    dc = dataclasses.dataclass(**kwargs)

    if not cls_frame:
        current_frame = inspect.currentframe()
        if current_frame:
            cls_frame = current_frame.f_back
        # Per https://docs.python.org/3/library/inspect.html#the-interpreter-stack
        del current_frame

    def decorate(cls: Type[_U]) -> Type[_U]:
        return add_schema(dc(cls), base_schema, cls_frame=cls_frame)

    if _cls is None:
        return decorate
    return decorate(cls)

@crutcher
Copy link

I tried to address some of the comments here in another PR:
#278

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants